/* Copyright (c) 2010 OFXKit
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#import "OFX401kContribution.h"
#import "OFX401kLoan.h"
#import "OFX401kVestingInfo.h"
#import "OFXAccount.h"
#import "OFXAccountInfo.h"
#import "OFXAmortizationTransaction.h"
#import "OFXApplication.h"
#import "OFXAssetClassPortion.h"
#import "OFXBalance.h"
#import "OFXBankAccount.h"
#import "OFXBuyDebt.h"
#import "OFXBuyMutualFund.h"
#import "OFXBuyOption.h"
#import "OFXBuyStock.h"
#import "OFXCloseOptionTransaction.h"
#import "OFXCurrency.h"
#import "OFXDebt.h"
#import "OFXDocumentResponse.h"
#import "OFXFinancialInstitution.h"
#import "OFXImage.h"
#import "OFX401kSummaryInfo.h"
#import "OFX401kInvestmentStatement.h"
#import "OFXInvestmentAccount.h"
#import "OFXInvestmentBankTransaction.h"
#import "OFXInvestmentBuyTransaction.h"
#import "OFXInvestmentIncomeExpense.h"
#import "OFXInvestmentIncomeTransaction.h"
#import "OFXInvestmentOpenOrder.h"
#import "OFXInvestmentOrderBuyDebt.h"
#import "OFXInvestmentOrderBuyMutualFund.h"
#import "OFXInvestmentOrderBuyOption.h"
#import "OFXInvestmentOrderBuyOther.h"
#import "OFXInvestmentOrderBuyStock.h"
#import "OFXInvestmentOrderSellDebt.h"
#import "OFXInvestmentOrderSellMutualFund.h"
#import "OFXInvestmentOrderSellOption.h"
#import "OFXInvestmentOrderSellOther.h"
#import "OFXInvestmentOrderSellStock.h"
#import "OFXInvestmentOrderSwithMutualFund.h"
#import "OFXInvestmentPosition.h"
#import "OFXInvestmentPositionDebt.h"
#import "OFXInvestmentPositionMutualFund.h"
#import "OFXInvestmentPositionOption.h"
#import "OFXInvestmentPositionOther.h"
#import "OFXInvestmentPositionStock.h"
#import "OFXInvestmentSellTransaction.h"
#import "OFXInvestmentSplit.h"
#import "OFXInvestmentStatement.h"
#import "OFXInvestmentTransaction.h"
#import "OFXInvestmentTransfer.h"
#import "OFXJournalFunds.h"
#import "OFXJournalSecurity.h"
#import "OFXLoanAccount.h"
#import "OFXLoanStatement.h"
#import "OFXLoanTransaction.h"
#import "OFXMarginInterest.h"
#import "OFXMultiFactorAuthentication.h"
#import "OFXMutualFund.h"
#import "OFXObject.h"
#import "OFXOption.h"
#import "OFXOtherSecurity.h"
#import "OFXResponseParser.h"
#import "OFXPayee.h"
#import "OFXReinvestTransaction.h"
#import "OFXReturnOfCapital.h"
#import "OFXSecurity.h"
#import "OFXSellDebt.h"
#import "OFXSellMutualFund.h"
#import "OFXSellOption.h"
#import "OFXSellStock.h"
#import "OFXSignOn.h"
#import "OFXStatement.h"
#import "OFXStatus.h"
#import "OFXStock.h"
#import "OFXTransaction.h"

@implementation OFXResponseParser

@synthesize inInvBankTran;
@synthesize invBankTranFundingSubAccount;
@synthesize booleanTranslationTable;
@synthesize dateTranslationTable;
@synthesize document;
@synthesize currentObject;
@synthesize currentElement;
@synthesize messageSet;
@synthesize numberTranslationTable;
@synthesize objectStack;
@synthesize parentElement;
@synthesize securities;
@synthesize stringTranslationTable;
@synthesize urlTranslationTable;

- (id) initWithDocument:(OFXDocumentResponse *)ofxDocument
{
  self = [super init];
  if (self == nil) return nil;
  
  [self setDocument:ofxDocument];
  [self setObjectStack:[[NSMutableArray alloc] init]];
  [self setStringTranslationTable:[NSMutableDictionary 
                                   dictionaryWithCapacity:50]];
  [self setSecurities:[[NSMutableDictionary alloc] init]];
  [self setInInvBankTran:NO];
  [self setInvBankTranFundingSubAccount:nil];
  
  // sign on response
  [stringTranslationTable setValue:@"userKey" forKey:@"USERKEY"];
  [stringTranslationTable setValue:@"accessKey" forKey:@"ACCESSKEY"];
  [stringTranslationTable setValue:@"userId" forKey:@"USERID"];
  [stringTranslationTable setValue:@"userPassword" forKey:@"USERPASS"];
  [stringTranslationTable setValue:@"sessionCookie" forKey:@"SESSCOOKIE"];
  [stringTranslationTable setValue:@"generateUserKey" forKey:@"GENUSERKEY"];
  [stringTranslationTable setValue:@"userCredential1" forKey:@"USERCRED1"];
  [stringTranslationTable setValue:@"userCredential2" forKey:@"USERCRED2"];
  [stringTranslationTable setValue:@"code" forKey:@"CODE"];
  [stringTranslationTable setValue:@"severity" forKey:@"SEVERITY"];
  [stringTranslationTable setValue:@"transactionUID" forKey:@"TRNUID"];
  [stringTranslationTable setValue:@"currencySymbol" forKey:@"CURDEF"];
  [stringTranslationTable setValue:@"currencySymbol" forKey:@"CURSYM"];
  [stringTranslationTable setValue:@"bankId" forKey:@"BANKID"];
  [stringTranslationTable setValue:@"branchId" forKey:@"BRANCHID"];
  [stringTranslationTable setValue:@"accountId" forKey:@"ACCTID"];
  [stringTranslationTable setValue:@"accountType" forKey:@"ACCTTYPE"];
  [stringTranslationTable setValue:@"accountKey" forKey:@"ACCTKEY"];
  [stringTranslationTable setValue:@"transactionType" forKey:@"TRNTYPE"];
  [stringTranslationTable setValue:@"uniqueId" forKey:@"FITID"];
  [stringTranslationTable setValue:@"uniqueId" forKey:@"UNIQUEID"];
  [stringTranslationTable setValue:@"uniqueIdType" forKey:@"UNIQUEIDTYPE"];
  [stringTranslationTable setValue:@"correctingUniqueId" forKey:@"CORRECTFITID"];
  [stringTranslationTable setValue:@"correctingAction" forKey:@"CORRECTACTION"];
  [stringTranslationTable setValue:@"serverTransactionId" forKey:@"SRVRTID"];
  [stringTranslationTable setValue:@"checkNumber" forKey:@"CHECKNUM"];
  [stringTranslationTable setValue:@"referenceNumber" forKey:@"REFNUM"];
  [stringTranslationTable setValue:@"standardIndustrialCode" forKey:@"SIC"];
  [stringTranslationTable setValue:@"payeeId" forKey:@"PAYEEID"];
  [stringTranslationTable setValue:@"name" forKey:@"NAME"];
  [stringTranslationTable setValue:@"address1" forKey:@"ADDR1"];
  [stringTranslationTable setValue:@"address2" forKey:@"ADDR2"];
  [stringTranslationTable setValue:@"address3" forKey:@"ADDR3"];
  [stringTranslationTable setValue:@"city" forKey:@"CITY"];
  [stringTranslationTable setValue:@"state" forKey:@"STATE"];
  [stringTranslationTable setValue:@"postalCode" forKey:@"POSTALCODE"];
  [stringTranslationTable setValue:@"country" forKey:@"COUNTRY"];
  [stringTranslationTable setValue:@"phone" forKey:@"PHONE"];
  [stringTranslationTable setValue:@"transactionDescription" forKey:@"EXTDNAME"];
  [stringTranslationTable setValue:@"memo" forKey:@"MEMO"];
  [stringTranslationTable setValue:@"currencySymbol" forKey:@"CURRSYM"];
  [stringTranslationTable setValue:@"imageType" forKey:@"IMAGETYPE"];
  [stringTranslationTable setValue:@"imageReferenceType" forKey:@"IMAGEREFTYPE"];
  [stringTranslationTable setValue:@"checkImageType" forKey:@"CHECKSUP"];
  [stringTranslationTable setValue:@"brokerId" forKey:@"BROKERID"];
  [stringTranslationTable setValue:@"securitySubAccount" forKey:@"SUBACCTSEC"];
  [stringTranslationTable setValue:@"buyType" forKey:@"BUYTYPE"];
  [stringTranslationTable setValue:@"buyType" forKey:@"OPTBUYTYPE"];
  [stringTranslationTable setValue:@"name" forKey:@"SECNAME"];
  [stringTranslationTable setValue:@"tickerSymbol" forKey:@"TICKER"];
  [stringTranslationTable setValue:@"mutualFundType" forKey:@"MFTYPE"];
  [stringTranslationTable setValue:@"positionType" forKey:@"POSTYPE"];
  [stringTranslationTable setValue:@"heldInAccount" forKey:@"HELDINACCT"];
  [stringTranslationTable setValue:@"balanceDescription" forKey:@"DESC"];
  [stringTranslationTable setValue:@"balanceType" forKey:@"BALTYPE"];
  [stringTranslationTable setValue:@"phraseId" forKey:@"MFAPHRASEID"];
  [stringTranslationTable setValue:@"phraseLabel" forKey:@"MFAPHRASELABEL"];
  [stringTranslationTable setValue:@"organization" forKey:@"ORG"];
  [stringTranslationTable setValue:@"financialInstitutionId" forKey:@"FID"];
  [stringTranslationTable setValue:@"relatedFinancialInstitutionId" forKey:@"RELFITID"];  
  [stringTranslationTable setValue:@"action" forKey:@"OPTACTION"];
  [stringTranslationTable setValue:@"incomeType" forKey:@"INCOMETYPE"];
  [stringTranslationTable setValue:@"sellReason" forKey:@"SELLREASON"];
  [stringTranslationTable setValue:@"sellType" forKey:@"SELLTYPE"];
  [stringTranslationTable setValue:@"sellType" forKey:@"OPTSELLTYPE"];
  [stringTranslationTable setValue:@"relatedType" forKey:@"RELTYPE"];
  [stringTranslationTable setValue:@"securedType" forKey:@"SECURED"];
  [stringTranslationTable setValue:@"transferAction" forKey:@"TFERACTION"];
  [stringTranslationTable setValue:@"reversingUniqueId" forKey:@"REVERSALFITID"];
  [stringTranslationTable setValue:@"financialInstitutionSecurityId" forKey:@"FIID"];
  [stringTranslationTable setValue:@"subAccount" forKey:@"SUBACCT"];
  [stringTranslationTable setValue:@"orderDuration" forKey:@"DURATION"];
  [stringTranslationTable setValue:@"orderRestrictions" forKey:@"RESTRICTION"];
  [stringTranslationTable setValue:@"unitType" forKey:@"UNITTYPE"];
  [stringTranslationTable setValue:@"rating" forKey:@"RATING"];
  [stringTranslationTable setValue:@"debtType" forKey:@"DEBTTYPE"];
  [stringTranslationTable setValue:@"debtClassification" forKey:@"DEBTCLASS"];
  [stringTranslationTable setValue:@"maturityRate" forKey:@"COUPONFREQ"];
  [stringTranslationTable setValue:@"callType" forKey:@"CALLTYPE"];
  [stringTranslationTable setValue:@"assetClass" forKey:@"ASSETCLASS"];
  [stringTranslationTable setValue:@"optionType" forKey:@"OPTTYPE"];
  [stringTranslationTable setValue:@"typeDescription" forKey:@"TYPEDESC"];
  [stringTranslationTable setValue:@"stockType" forKey:@"STOCKTYPE"];
  [stringTranslationTable setValue:@"usProductType" forKey:@"USPRODUCTTYPE"];
  [stringTranslationTable setValue:@"accountType" forKey:@"INVACCTTYPE"];
  [stringTranslationTable setValue:@"optionTradingLevel" forKey:@"OPTIONLEVEL"];
  [stringTranslationTable setValue:@"serviceStatus" forKey:@"SVCSTATUS"];
  [stringTranslationTable setValue:@"loanId" forKey:@"LOANID"];
  [stringTranslationTable setValue:@"employerName" forKey:@"EMPLOYERNAME"];
  [stringTranslationTable setValue:@"planId" forKey:@"PLANID"];
  [stringTranslationTable setValue:@"employerContactInfo"
                            forKey:@"EMPLOYERCONTACTINFO"];
  [stringTranslationTable setValue:@"brokerContactInfo"
                            forKey:@"BROKERCONTACTINFO"];
  [stringTranslationTable setValue:@"loanId" forKey:@"LOANID"];
  [stringTranslationTable setValue:@"loanDescription"
                            forKey:@"LOANDESC"];
  [stringTranslationTable setValue:@"investment401kSource"
                            forKey:@"INV401KSOURCE"];
  
  [self setBooleanTranslationTable:[NSMutableDictionary dictionaryWithCapacity:10]];
  [booleanTranslationTable setValue:@"taxExempt" forKey:@"TAXEXEMPT"];
  [booleanTranslationTable setValue:@"reinvestDividend" forKey:@"REINVDIV"];
  [booleanTranslationTable setValue:@"reinvestCapitalGains" forKey:@"REINVCG"];
  [booleanTranslationTable setValue:@"buyAtAuction" forKey:@"AUCTION"];
  [booleanTranslationTable setValue:@"sellAll" forKey:@"SELLALL"];
  [booleanTranslationTable setValue:@"swithAll" forKey:@"SWITCHALL"];
  [booleanTranslationTable setValue:@"checkWritingEnabled" forKey:@"CHECKING"];
  [booleanTranslationTable setValue:@"priorYear401kContribution" 
                             forKey:@"PRIORYEARCONTRIB"];
  
  [self setDateTranslationTable:[NSMutableDictionary dictionaryWithCapacity:10]];
  [dateTranslationTable setValue:@"dateStart" forKey:@"DTSTART"];
  [dateTranslationTable setValue:@"dateEnd" forKey:@"DTEND"];
  [dateTranslationTable setValue:@"datePosted" forKey:@"DTPOSTED"];
  [dateTranslationTable setValue:@"dateInitiated" forKey:@"DTUSER"];
  [dateTranslationTable setValue:@"dateAvailable" forKey:@"DTAVAIL"];
  [dateTranslationTable setValue:@"dateAvailable" forKey:@"DTIMAGEAVAIL"];
  [dateTranslationTable setValue:@"tradeDate" forKey:@"DTTRADE"];
  [dateTranslationTable setValue:@"priceAndValueValidAsOf" 
                          forKey:@"DTPRICEASOF"];
  [dateTranslationTable setValue:@"settlementDate" forKey:@"DTSETTLE"];
  [dateTranslationTable setValue:@"userKeyExpires" forKey:@"TSKEYEXPIRE"]; 
  [dateTranslationTable setValue:@"dateProfileUpdated" forKey:@"DTPROFUP"];
  [dateTranslationTable setValue:@"dateAccountUpdated" forKey:@"DTACCTUP"];
  [dateTranslationTable setValue:@"datePurchased" forKey:@"DTPURCHASE"];
  [dateTranslationTable setValue:@"datePlaced" forKey:@"DTPLACED"];
  [dateTranslationTable setValue:@"dateOfAuction" forKey:@"DTAUCTION"];
  [dateTranslationTable setValue:@"maturityDateOfCoupon" forKey:@"DTCOUPON"];
  [dateTranslationTable setValue:@"callDate" forKey:@"DTCALL"];
  [dateTranslationTable setValue:@"maturityDate" forKey:@"DTMAT"];
  [dateTranslationTable setValue:@"yieldValidAsOf" forKey:@"DTYIELDASOF"];
  [dateTranslationTable setValue:@"expirationDate" forKey:@"DTEXPIRE"];
  [dateTranslationTable setValue:@"dateOfPayrollDeduction" 
                          forKey:@"DTPAYROLL"];
  [dateTranslationTable setValue:@"planJoinDate" forKey:@"PLANJOINDATE"];
  [dateTranslationTable setValue:@"startOfYear" forKey:@"STARTOFYEAR"];
  [dateTranslationTable setValue:@"startDate" forKey:@"LOANSTARTDATE"];
  [dateTranslationTable setValue:@"maturityDate" forKey:@"LOANMATURITYDATE"];
  [dateTranslationTable setValue:@"nextPaymentDate" forKey:@"LOANNEXTPMTDATE"];
  
  [self setNumberTranslationTable:[NSMutableDictionary dictionaryWithCapacity:10]];
  [numberTranslationTable setValue:@"amount" forKey:@"TRNAMT"];
  [numberTranslationTable setValue:@"imageTTL" forKey:@"IMAGETTL"];
  [numberTranslationTable setValue:@"units" forKey:@"UNITS"];
  [numberTranslationTable setValue:@"unitPrice" forKey:@"UNITPRICE"];
  [numberTranslationTable setValue:@"unitsInFinancialInstitutionStreetName"
                            forKey:@"UNITSSTREET"];
  [numberTranslationTable setValue:@"unitInUsersName" forKey:@"UNITSUSER"];
  [numberTranslationTable setValue:@"commission" forKey:@"COMMISSION"];
  [numberTranslationTable setValue:@"unitPriceMarkUp" forKey:@"MARKUP"];
  [numberTranslationTable setValue:@"unitPriceMarkDown" forKey:@"MARKDOWN"];
  [numberTranslationTable setValue:@"taxes" forKey:@"TAXES"];
  [numberTranslationTable setValue:@"fees" forKey:@"FEES"];
  [numberTranslationTable setValue:@"load" forKey:@"LOAD"];
  [numberTranslationTable setValue:@"totalAmount" forKey:@"TOTAL"];
  [numberTranslationTable setValue:@"marketValue" forKey:@"MKTVAL"];
  [numberTranslationTable setValue:@"availableBalance" forKey:@"AVAILCASH"];
  [numberTranslationTable setValue:@"marginBalance" forKey:@"MARGINBALANCE"];
  [numberTranslationTable setValue:@"shortBalance" forKey:@"SHORTBALANCE"];
  [numberTranslationTable setValue:@"value" forKey:@"VALUE"];
  [numberTranslationTable setValue:@"accruedInterest" forKey:@"ACCRDINT"];
  [numberTranslationTable setValue:@"sharesPerContract" forKey:@"SHPERCTRCT"];
  [numberTranslationTable setValue:@"gain" forKey:@"GAIN"];
  [numberTranslationTable setValue:@"taxWithholding" forKey:@"WITHHOLDING"];
  [numberTranslationTable setValue:@"penaltyWithholding" forKey:@"PENALTY"];
  [numberTranslationTable setValue:@"averageCostBasis" forKey:@"AVGCOSTBASIS"];
  [numberTranslationTable setValue:@"unitsOld" forKey:@"OLDUNITS"];
  [numberTranslationTable setValue:@"unitsNew" forKey:@"NEWUNITS"];
  [numberTranslationTable setValue:@"splitRatioNumerator" forKey:@"NUMERATOR"];
  [numberTranslationTable setValue:@"splitRatioDenominator" forKey:@"DENOMINATOR"];
  [numberTranslationTable setValue:@"fractionalUnitsCash" forKey:@"FRACCASH"];
  [numberTranslationTable setValue:@"minUnits" forKey:@"MINUNITS"];
  [numberTranslationTable setValue:@"limitPrice" forKey:@"LIMITPRICE"];
  [numberTranslationTable setValue:@"stopPrice" forKey:@"STOPPRICE"];
  [numberTranslationTable setValue:@"parValue" forKey:@"PARVALUE"];
  [numberTranslationTable setValue:@"couponRate" forKey:@"COUPONRT"];
  [numberTranslationTable setValue:@"callPrice" forKey:@"CALLPRICE"];
  [numberTranslationTable setValue:@"yieldToCall" forKey:@"YIELDTOCALL"];
  [numberTranslationTable setValue:@"yieldToMaturity" forKey:@"YIELDTOMAT"];
  [numberTranslationTable setValue:@"yield" forKey:@"YIELD"];
  [numberTranslationTable setValue:@"portion" forKey:@"PERCENT"];
  [numberTranslationTable setValue:@"strikePrice" forKey:@"STRIKEPRICE"];
  [numberTranslationTable setValue:@"stateWithholding" 
                            forKey:@"STATEWITHHOLDING"];
  [numberTranslationTable setValue:@"withholding" forKey:@"WITHHOLDING"];
  [numberTranslationTable setValue:@"penalty" forKey:@"PENALTY"];
  [numberTranslationTable setValue:@"loanPrincipal" forKey:@"LOANPRINCIPAL"];
  [numberTranslationTable setValue:@"loanInterestRate" forKey:@"LOANINTEREST"];
  [numberTranslationTable setValue:@"cashBalance" forKey:@"CASHBAL"];
  [numberTranslationTable setValue:@"percentSalaryDeferredBeforeTax"
                            forKey:@"DEFERPCTPRETAX"];
  [numberTranslationTable setValue:@"percentSalaryDeferredAfterTax"
                            forKey:@"DEFERPCTAFTERTAX"];
  [numberTranslationTable setValue:@"dollarForDollarMatchPercent"
                            forKey:@"MATCHPCT"];
  [numberTranslationTable setValue:@"maxMatchAmount" forKey:@"MAXMATCHAMT"];
  [numberTranslationTable setValue:@"maxMatchPercent" forKey:@"MAXMATCHPCT"];
  [numberTranslationTable setValue:@"baseMatchAmount" forKey:@"BASEMATCHAMT"];
  [numberTranslationTable setValue:@"baseMatchPercent" forKey:@"BASEMATCHPCT"];
  [numberTranslationTable setValue:@"preTaxContributionPercent"
                            forKey:@"PRETAXCONTRIBPCT"];
  [numberTranslationTable setValue:@"preTaxContributionAmount"
                            forKey:@"PRETAXCONTRIBAMT"];
  [numberTranslationTable setValue:@"afterTaxContributionPercent"
                            forKey:@"AFTERTAXCONTRIBPCT"];
  [numberTranslationTable setValue:@"afterTaxContributionAmount"
                            forKey:@"AFTERTAXCONTRIBAMT"];
  [numberTranslationTable setValue:@"matchTaxContributionPercent"
                            forKey:@"MATCHTAXCONTRIBPCT"];
  [numberTranslationTable setValue:@"matchTaxContributionAmount"
                            forKey:@"MATCHTAXCONTRIBAMT"];
  [numberTranslationTable setValue:@"profitSharingContributionPercent"
                            forKey:@"PROFITSHARINGCONTRIBPCT"];
  [numberTranslationTable setValue:@"profitSharingContributionAmount"
                            forKey:@"PROFITSHARINGCONTRIBAMT"];
  [numberTranslationTable setValue:@"rolloverContributionPercent"
                            forKey:@"ROLLOVERCONTRIBPCT"];
  [numberTranslationTable setValue:@"rolloverContributionAmount"
                            forKey:@"ROLLOVERCONTRIBAMT"];
  [numberTranslationTable setValue:@"otherVestingContributionPercent"
                            forKey:@"OTHERVESTPCT"];
  [numberTranslationTable setValue:@"otherVestingContributionAmount"
                            forKey:@"OTHERVESTAMT"];
  [numberTranslationTable setValue:@"otherNonVestingContributionPercent"
                            forKey:@"OTHERNONVESTPCT"];
  [numberTranslationTable setValue:@"otherNonVestingContributionAmount"
                            forKey:@"OTHERNONVESTAMT"];
  [numberTranslationTable setValue:@"currentVestedPercent"
                            forKey:@"CURRENTVESTPCT"];
  [numberTranslationTable setValue:@"initialBalance" forKey:@"INITIALLOANBAL"];
  [numberTranslationTable setValue:@"currentBalance" forKey:@"CURRENTLOANBAL"];
  [numberTranslationTable setValue:@"annualInterestRate" forKey:@"LOANRATE"];
  [numberTranslationTable setValue:@"paymentAmount" forKey:@"LOANPMTAMT"];
  [numberTranslationTable setValue:@"paymentFrequency" forKey:@"LOANPMTFREQ"];
  [numberTranslationTable setValue:@"initialNumberLoanPayments"
                            forKey:@"LOANPMTSINITIAL"];
  [numberTranslationTable setValue:@"paymentsRemaining" 
                            forKey:@"LOANPMTSREMAINING"];
  [numberTranslationTable setValue:@"totalProjectedInterest"
                            forKey:@"LOANTOTALPROJINTEREST"];
  [numberTranslationTable setValue:@"totalInterestToDate" 
                            forKey:@"LOAINTERESTTODATE"];
  [numberTranslationTable setValue:@"exchangeRate" forKey:@"CURRATE"];

  
  [self setUrlTranslationTable:[NSMutableDictionary dictionaryWithCapacity:10]];
  [urlTranslationTable setValue:@"imageReference" forKey:@"IMAGEREF"];
  
  return self;
}

- (NSDate *) parseDateFromString:(NSString *)dateString
{
  NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
  [dateFormatter setTimeZone:[NSTimeZone timeZoneWithName:@"GMT"]];
  // Even though the OFX specification calls for only one date format, several 
  // variants have been observed in practice.
  NSArray* dateFormatStrings = [NSArray arrayWithObjects:@"yyyyMMddHHmmss.SSS[Z:z]",
                                @"yyyyMMddHHmmss.SSS [Z:z]", @"yyyyMMdd", 
                                @"yyyyMMddHHmmss", @"yyyyMMddHHmm", nil];
  for (NSString* dateFormat in dateFormatStrings) {
    [dateFormatter setDateFormat:dateFormat];
    NSDate* result = [dateFormatter dateFromString:dateString];
    if (result != nil) return result;
  }
  
  return nil;
}

- (void) parserDidEndDocument:(AQXMLParser *)parser
{
  // finished parsing all elements now do any clean-up tasks necessary.
  
  // add full information for all securities
  for (OFXStatement* statement in [document statements]) {
    if ([statement isKindOfClass:[OFXInvestmentStatement class]]) {
      for (OFXTransaction* transaction in [statement transactions]) {
        if ([transaction isKindOfClass:[OFXInvestmentTransaction class]]) {
          OFXSecurity* security = [transaction valueForKey:@"security"];
          NSString* securityId = [NSString stringWithFormat:@"%@@%@", 
                                  [security uniqueId], [security uniqueIdType]];
          OFXSecurity* newSecurity = [securities valueForKey:securityId];
          if (newSecurity != nil) {
            [transaction setValue:newSecurity forKey:@"security"];
          }
        }
      }
      
      for (OFXInvestmentPosition* position in 
          [statement valueForKey:@"positions"]) {
        OFXSecurity* security = [position valueForKey:@"security"];
        NSString* securityId = [NSString stringWithFormat:@"%@@%@", 
                                [security uniqueId], [security uniqueIdType]];
        OFXSecurity* newSecurity = [securities valueForKey:securityId];
        if (newSecurity != nil) {
          [position setValue:newSecurity forKey:@"security"];
        }
      }
      
      for (OFXInvestmentOpenOrder* order in 
           [statement valueForKey:@"openOrders"]) {
        OFXSecurity* security = [order valueForKey:@"security"];
        NSString* securityId = [NSString stringWithFormat:@"%@@%@", 
                                [security uniqueId], [security uniqueIdType]];
        OFXSecurity* newSecurity = [securities valueForKey:securityId];
        if (newSecurity != nil) {
          [order setValue:newSecurity forKey:@"security"];
        }
      }
    }
  }
  
  NSArray* secArray = [securities allValues];
  NSMutableArray* secList = [NSMutableArray arrayWithArray:secArray];
  [document setSecuritiesList:secList];
}

- (void) parser:(AQXMLParser *)parser 
didStartElement:(NSString *)elementName 
   namespaceURI:(NSString *)namespaceURI 
  qualifiedName:(NSString *)qualifiedName 
     attributes:(NSDictionary *)attributeDict
{    
  [self setCurrentElement:elementName];
  // Handle message set responses
  if ([elementName isEqualToString:@"SIGNONMSGSRSV1"]) {
    // sign on message set response
    [self setMessageSet:elementName];
  } else if ([elementName isEqualToString:@"SIGNUPMSGSRSV1"]) {
    // sign up message set response
    [self setMessageSet:elementName];
  } else if ([elementName isEqualToString:@"BANKMSGSRSV1"]) {
    // bank message set response
    [self setMessageSet:elementName];
  } else if ([elementName isEqualToString:@"CREDITCARDMSGSRSV1"]) {
    // credit card message set response
    [self setMessageSet:elementName];
  } else if ([elementName isEqualToString:@"LOANMSGSRSV1"]) {
    // loan message set response
    [self setMessageSet:elementName];
  } else if ([elementName isEqualToString:@"INVSTMTMSGSRV1"]) {
    // investment message set response
    [self setMessageSet:elementName];
  } else if ([elementName isEqualToString:@"INTERXFERMSGSRSV1"]) {
    // interbank funds transfer message set response
    [self setMessageSet:elementName];
  } else if ([elementName isEqualToString:@"WIREXFERMSGSRSV1"]) {
    // wire funds transfer message set response
    [self setMessageSet:elementName];
  } else if ([elementName isEqualToString:@"BILLPAYMSGRSV1"]) {
    // bill pay message set response
    [self setMessageSet:elementName];
  } else if ([elementName isEqualToString:@"EMAILMSGSRSV1"]) {
    // general e-mail message set response
    // not currently implemented
  } else if ([elementName isEqualToString:@"SECLISTMSGSRSV1"]) {
    // security list message set response
    [self setMessageSet:elementName];
  } else if ([elementName isEqualToString:@"PRESDIRMSGRSV1"]) {
    // biller directory message set response
    [self setMessageSet:elementName];
  } else if ([elementName isEqualToString:@"PRESDLVMSGRSV1"]) {
    // bill delivery message set response
    [self setMessageSet:elementName];
  } else if ([elementName isEqualToString:@"PROFMSGRSV1"]) {
    // financial institution message set response
    [self setMessageSet:elementName];
  } else if ([elementName isEqualToString:@"IMAGEMSGRSV1"]) {
    // image download message set response
    [self setMessageSet:elementName];
  } 
  
  // handle sign on request
  else if ([elementName isEqualToString:@"SONRS"]) {
    // signon response
    if (currentObject != nil) {
      [objectStack addObject:currentObject];
    }
    
    // check if the sign on response for this document has already
    // been set.
    if ([document signOnResponse] == nil) {
      [self setCurrentObject:[[OFXSignOn alloc] init]];
    } else {
      [self setCurrentObject:[document signOnResponse]];
    }
  }
  
  else if ([elementName isEqualToString:@"MFACHALLENGE"]) {
    [self setCurrentObject:[[OFXMultiFactorAuthentication alloc] init]];
  }
  
  // handle status aggregate
  else if ([elementName isEqualToString:@"STATUS"]) {
    // status element
    if (currentObject != nil) {
      [objectStack addObject:currentObject];
    }
    [self setCurrentObject:[[OFXStatus alloc] init]];
  } else if ([elementName isEqualToString:@"CURDEF"]) {
    if (currentObject != nil) {
      [objectStack addObject:currentObject];
    }
    [self setCurrentObject:[[OFXCurrency alloc] init]];
  } else if ([elementName isEqualToString:@"CURRENCY"] ||
             [elementName isEqualToString:@"ORIGCURRENCY"]) {
    if (currentObject != nil) {
      [objectStack addObject:currentObject];
    }
    [self setCurrentObject:[[OFXCurrency alloc] init]];
  } else if ([elementName isEqualToString:@"IMAGEDATA"]) {
    if (currentObject != nil) {
      [objectStack addObject:currentObject];
    }
    [self setCurrentObject:[[OFXImage alloc] init]];
  } 
  
  else if ([elementName isEqualToString:@"ACCTINFOTRNRS"]) {
    currentObject = [[OFXAccountInfo alloc] init];
  }
  
  else if ([elementName isEqualToString:@"INVACCTFROM"]) {
    if (![parentElement isEqualToString:@"INVACCTINFO"]) {
      [objectStack addObject:currentObject];
      [self setCurrentObject:[[OFXInvestmentAccount alloc] init]];
    }
  } else if ([elementName isEqualToString:@"INVACCTINFO"]) {
    [self setParentElement:elementName];
    [objectStack addObject:currentObject];
    [self setCurrentObject:[[OFXInvestmentAccount alloc] init]];
  } else if ([elementName isEqualToString:@"BANKACCTINFO"] ||
             [elementName isEqualToString:@"CCACCTINFO"]) {
    [self setParentElement:elementName];
  } else if ([elementName isEqualToString:@"VESTINFO"]) {
    [objectStack addObject:currentObject];
    [self setCurrentObject:[[OFX401kVestingInfo alloc] init]];
  } else if ([elementName isEqualToString:@"LOANINFO"]) {
    [self setParentElement:elementName];
    [objectStack addObject:currentObject];
    [self setCurrentObject:[[OFX401kLoan alloc] init]];
  } else if ([elementName isEqualToString:@"YEARTODATE"] ||
             [elementName isEqualToString:@"INCEPTIONTODATE"] ||
             [elementName isEqualToString:@"PERIODTODATE"]) {
    [objectStack addObject:currentObject];
    [self setCurrentObject:[[OFX401kSummaryInfo alloc] init]];
  } else if ([elementName isEqualToString:@"CONTRIBUTIONS"] || 
             [elementName isEqualToString:@"WITHDRAWALS"] || 
             [elementName isEqualToString:@"EARNINGS"]) {
    [self setParentElement:elementName];
  }
  
  else if ([elementName isEqualToString:@"BUYDEBT"]) {
    [objectStack addObject:currentObject];
    [self setCurrentObject:[[OFXBuyDebt alloc] init]];
  } else if ([elementName isEqualToString:@"BUYMF"]) {
    [objectStack addObject:currentObject];
    [self setCurrentObject:[[OFXBuyMutualFund alloc] init]];
  } else if ([elementName isEqualToString:@"BUYOPT"]) {
    [objectStack addObject:currentObject];
    [self setCurrentObject:[[OFXBuyOption alloc] init]];
  } else if ([elementName isEqualToString:@"BUYOTHER"]) {
    [objectStack addObject:currentObject];
    [self setCurrentObject:[[OFXInvestmentBuyTransaction alloc] init]];
  } else if ([elementName isEqualToString:@"BUYSTOCK"]) {
    [objectStack addObject:currentObject];
    [self setCurrentObject:[[OFXBuyStock alloc] init]];
  } else if ([elementName isEqualToString:@"INVBANKTRAN"]) {
    [self setInInvBankTran:YES];
  } else if ([elementName isEqualToString:@"CLOSUREOPT"]) {
    [objectStack addObject:currentObject];
    [self setCurrentObject:[[OFXCloseOptionTransaction alloc] init]];
  } else if ([elementName isEqualToString:@"INCOME"]) {
    [objectStack addObject:currentObject];
    [self setCurrentObject:[[OFXInvestmentIncomeTransaction alloc] init]];
  } else if ([elementName isEqualToString:@"INVEXPENSE"]) {
    [objectStack addObject:currentObject];
    [self setCurrentObject:[[OFXInvestmentIncomeExpense alloc] init]];
  } else if ([elementName isEqualToString:@"JRNLFUND"]) {
    [objectStack addObject:currentObject];
    [self setCurrentObject:[[OFXJournalFunds alloc] init]];
  } else if ([elementName isEqualToString:@"JRNLSEC"]) {
    [objectStack addObject:currentObject];
    [self setCurrentObject:[[OFXJournalSecurity alloc] init]];
  } else if ([elementName isEqualToString:@"MARGININTEREST"]) {
    [objectStack addObject:currentObject];
    [self setCurrentObject:[[OFXMarginInterest alloc] init]];
  } else if ([elementName isEqualToString:@"REINVEST"]) {
    [objectStack addObject:currentObject];
    [self setCurrentObject:[[OFXReinvestTransaction alloc] init]];
  } else if ([elementName isEqualToString:@"RETOFCAP"]) {
    [objectStack addObject:currentObject];
    [self setCurrentObject:[[OFXReturnOfCapital alloc] init]];
  } else if ([elementName isEqualToString:@"SELLDEBT"]) {
    [objectStack addObject:currentObject];
    [self setCurrentObject:[[OFXSellDebt alloc] init]];
  } else if ([elementName isEqualToString:@"SELLMF"]) {
    [objectStack addObject:currentObject];
    [self setCurrentObject:[[OFXSellMutualFund alloc] init]];
  } else if ([elementName isEqualToString:@"SELLOPT"]) {
    [objectStack addObject:currentObject];
    [self setCurrentObject:[[OFXSellOption alloc] init]];
  } else if ([elementName isEqualToString:@"SELLOTHER"]) {
    [objectStack addObject:currentObject];
    [self setCurrentObject:[[OFXInvestmentSellTransaction alloc] init]];
  } else if ([elementName isEqualToString:@"SELLSTOCK"]) {
    [objectStack addObject:currentObject];
    [self setCurrentObject:[[OFXSellStock alloc] init]];
  } else if ([elementName isEqualToString:@"SPLIT"]) {
    [objectStack addObject:currentObject];
    [self setCurrentObject:[[OFXInvestmentSplit alloc] init]];
  } else if ([elementName isEqualToString:@"TRANSFER"]) {
    [objectStack addObject:currentObject];
    [self setCurrentObject:[[OFXInvestmentTransfer alloc] init]];
  }
  
  else if ([elementName isEqualToString:@"SECID"]) {
    if (![parentElement isEqualToString:@"SECINFO"]) {
      [objectStack addObject:currentObject];
      [self setCurrentObject:[[OFXSecurity alloc] init]];
    }
  } 
  
  else if ([elementName isEqualToString:@"OOBUYDEBT"]) {
    [objectStack addObject:currentObject];
    [self setCurrentObject:[[OFXInvestmentOrderBuyDebt alloc] init]];
  } else if ([elementName isEqualToString:@"OOBUYMF"]) {
    [objectStack addObject:currentObject];
    [self setCurrentObject:[[OFXInvestmentOrderBuyMutualFund alloc] init]];
  } else if ([elementName isEqualToString:@"OOBUYOPT"]) {
    [objectStack addObject:currentObject];
    [self setCurrentObject:[[OFXInvestmentOrderBuyOption alloc] init]];
  } else if ([elementName isEqualToString:@"OOBUYOTHER"]) {
    [objectStack addObject:currentObject];
    [self setCurrentObject:[[OFXInvestmentOrderBuyOther alloc] init]];
  } else if ([elementName isEqualToString:@"OOBUYSTOCK"]) {
    [objectStack addObject:currentObject];
    [self setCurrentObject:[[OFXInvestmentOrderBuyStock alloc] init]];
  } else if ([elementName isEqualToString:@"OOSELLDEBT"]) {
    [objectStack addObject:currentObject];
    [self setCurrentObject:[[OFXInvestmentOrderSellDebt alloc] init]];
  } else if ([elementName isEqualToString:@"OOSELLMF"]) {
    [objectStack addObject:currentObject];
    [self setCurrentObject:[[OFXInvestmentOrderSellMutualFund alloc] init]];
  } else if ([elementName isEqualToString:@"OOSELLOPT"]) {
    [objectStack addObject:currentObject];
    [self setCurrentObject:[[OFXInvestmentOrderSellOption alloc] init]];
  } else if ([elementName isEqualToString:@"OOSELLOTHER"]) {
    [objectStack addObject:currentObject];
    [self setCurrentObject:[[OFXInvestmentOrderSellOther alloc] init]];
  } else if ([elementName isEqualToString:@"OOSELLSTOCK"]) {
    [objectStack addObject:currentObject];
    [self setCurrentObject:[[OFXInvestmentOrderSellStock alloc] init]];
  } else if ([elementName isEqualToString:@"SWITCHMF"]) {
    [objectStack addObject:currentObject];
    [self setCurrentObject:[[OFXInvestmentOrderSwithMutualFund alloc] init]];
  }
  
  else if ([elementName isEqualToString:@"POSDEBT"]) {
    [objectStack addObject:currentObject];
    [self setCurrentObject:[[OFXInvestmentPositionDebt alloc] init]];
  } else if ([elementName isEqualToString:@"POSMF"]) {
    [objectStack addObject:currentObject];
    [self setCurrentObject:[[OFXInvestmentPositionMutualFund alloc] init]];  
  } else if ([elementName isEqualToString:@"POSOPT"]) {
    [objectStack addObject:currentObject];
    [self setCurrentObject:[[OFXInvestmentPositionOption alloc] init]];  
  } else if ([elementName isEqualToString:@"POSOTHER"]) {
    [objectStack addObject:currentObject];
    [self setCurrentObject:[[OFXInvestmentPositionOther alloc] init]];
  } else if ([elementName isEqualToString:@"POSSTOCK"]) {
    [objectStack addObject:currentObject];
    [self setCurrentObject:[[OFXInvestmentPositionStock alloc] init]];
  }
    
  
  else if ([elementName isEqualToString:@"STOCKINFO"]) {
    [self setCurrentObject:[[OFXStock alloc] init]];
  } else if ([elementName isEqualToString:@"MFINFO"]) {
    [self setCurrentObject:[[OFXMutualFund alloc] init]];
  } else if ([elementName isEqualToString:@"OPTINFO"]) {
    [self setCurrentObject:[[OFXOption alloc] init]];
  } else if ([elementName isEqualToString:@"DEBTINFO"]) {
    [self setCurrentObject:[[OFXDebt alloc] init]];
  } else if ([elementName isEqualToString:@"OTHERINFO"]) {
    [self setCurrentObject:[[OFXOtherSecurity alloc] init]];
  } else if ([elementName isEqualToString:@"SECINFO"]) {
    [self setParentElement:@"SECINFO"];
  } 
  
  else if ([elementName isEqualToString:@"INV401KBAL"] || 
           [elementName isEqualToString:@"INV401K"]) {
    [self setParentElement:elementName];
    // need to transform the statement to an Investment401kStatement
    if (![currentObject isKindOfClass:[OFX401kInvestmentStatement class]]) {
      OFXInvestmentStatement* statement = currentObject;
      OFX401kInvestmentStatement* statement401k = 
      [[OFX401kInvestmentStatement alloc] initWithInvestmentStatement:statement];
      currentObject = statement401k;
    }
  } else if ([elementName isEqualToString:@"CONTRIBSECURITY"]) {
    [objectStack addObject:currentObject];
    [self setCurrentObject:[[OFX401kContribution alloc] init]];
  }

  else if ([elementName isEqualToString:@"STMTTRNRS"] ||
           [elementName isEqualToString:@"CCSTMTTRNRS"]) {
    [self setCurrentObject:[[OFXStatement alloc] init]];
  } else if ([elementName isEqualToString:@"INVSTMTTRNRS"]) {
    [self setCurrentObject:[[OFXInvestmentStatement alloc] init]];
  } else if ([elementName isEqualToString:@"SMTTRS"] ||
             [elementName isEqualToString:@"CCSTMTRS"] ||
             [elementName isEqualToString:@"INVSTMTRS"]) {
    [self setParentElement:elementName];
  } else if ([elementName isEqualToString:@"BANKACCTFROM"]) {
    [objectStack addObject:currentObject];
    [self setCurrentObject:[[OFXBankAccount alloc] init]];
  } else if ([elementName isEqualToString:@"PAYEEID"]) {
    // create a payee ... but first we need to check if the current
    // object is of type payee
    if (![currentObject isKindOfClass:[OFXPayee class]]) {
      [objectStack addObject:currentObject];
      if ([currentObject valueForKey:@"payee"] != nil) {
        [self setCurrentObject:[currentObject valueForKey:@"payee"]];
      } else {
        [self setCurrentObject:[[OFXPayee alloc] init]];
      }
    }
  } else if ([elementName isEqualToString:@"PAYEE"]) {
    // create a payee ... but first we need to check if the current
    // object is of type payee
    [self setParentElement:elementName];
    if (![currentObject isKindOfClass:[OFXPayee class]]) {
      [objectStack addObject:currentObject];
      if ([currentObject valueForKey:@"payee"] != nil) {
        [self setCurrentObject:[currentObject valueForKey:@"payee"]];
      } else {
        [self setCurrentObject:[[OFXPayee alloc] init]];
      }
    }
  } else if ([elementName isEqualToString:@"NAME"]) {
    // name could be a member of several different types of aggegrates
    if (![currentObject isKindOfClass:[OFXPayee class]] && 
        [currentObject isKindOfClass:[OFXTransaction class]]) {
        [objectStack addObject:currentObject];
      if ([currentObject valueForKey:@"payee"] != nil) {
        [self setCurrentObject:[currentObject valueForKey:@"payee"]];
      } else {
          [self setCurrentObject:[[OFXPayee alloc] init]];
      }        
    }
  } else if ([elementName isEqualToString:@"LEDGERBAL"] || 
             [elementName isEqualToString:@"AVAILBAL"]) {
    [self setParentElement:elementName];
  } else if ([elementName isEqualToString:@"BALLIST"]) {
  } else if ([elementName isEqualToString:@"BAL"]) {
    [self setParentElement:elementName];
    [objectStack addObject:currentObject];
    [self setCurrentObject:[[OFXBalance alloc] init]];
  } else if ([elementName isEqualToString:@"STMTTRN"]) {
    [objectStack addObject:currentObject];
    if (inInvBankTran) {
      [self setCurrentObject:[[OFXInvestmentBankTransaction alloc] init]];
    } else {
      [self setCurrentObject:[[OFXTransaction alloc] init]];
    }
  } else if ([elementName isEqualToString:@"CCACCTFROM"]) {
    [objectStack addObject:currentObject];
    [self setCurrentObject:[[OFXAccount alloc] init]];
    [currentObject setValue:@"CREDITCARD" forKey:@"accountType"];
  } else if ([elementName isEqualToString:@"FI"]) {
    [objectStack addObject:currentObject];
    [self setCurrentObject:[[OFXFinancialInstitution alloc] init]];
  } else if ([elementName isEqualToString:@"PORTION"] || 
             [elementName isEqualToString:@"FIPORTION"]) {
    [self setParentElement:elementName];
    [objectStack addObject:currentObject];
    [self setCurrentObject:[[OFXAssetClassPortion alloc] init]];
  }
}

- (void) parser:(AQXMLParser *)parser
  didEndElement:(NSString *)elementName 
   namespaceURI:(NSString *)namespaceURI 
  qualifiedName:(NSString *)qualifiedName
{  
  [self setCurrentElement:nil];
  if ([elementName isEqualToString:@"SONRS"]) {
    // signon response
    [document setSignOnResponse:currentObject];
    [self setCurrentObject:[objectStack lastObject]];
    if (currentObject != nil) {
      [objectStack removeLastObject];
    }
    [self setCurrentObject:nil];
  } else if ([elementName isEqualToString:@"MFACHALLENGE"]) {
    if ([document signOnResponse] == nil) {
      [document setSignOnResponse:[[OFXSignOn alloc] init]];
    }
    [[[document signOnResponse] multiFactorAuthentication]
     addObject:currentObject];
    [self setCurrentObject:nil];
  } else if ([elementName isEqualToString:@"STATUS"]) {
    // status element
    OFXStatus *status = currentObject;
    [self setCurrentObject:[objectStack lastObject]];
    [objectStack removeLastObject];
    [currentObject setValue:status forKey:@"status"];
  } else if ([elementName isEqualToString:@"STMTTRNRS"]   ||
             [elementName isEqualToString:@"CCSTMTTRNRS"] ||
             [elementName isEqualToString:@"INVSTMTTRNRS"]) {
    [[document statements] addObject:currentObject];
    [self setCurrentObject:[objectStack lastObject]];
    if (currentObject != nil) {
      [objectStack removeLastObject];
    }
  } else if ([elementName isEqualToString:@"CONTRIBSECURITY"]) { 
    OFX401kContribution* contribution = currentObject;
    [self setCurrentObject:[objectStack lastObject]];
    if (currentObject != nil) {
      [[currentObject valueForKey:@"contributionInfo"] addObject:contribution];
      [objectStack removeLastObject];
    }
  } else if ([elementName isEqualToString:@"CURDEF"]) {
    OFXCurrency* currency = currentObject;
    [self setCurrentObject:[objectStack lastObject]];
    if (currentObject != nil) {
      [currentObject setValue:currency forKey:@"currency"];
      [objectStack removeLastObject];
    }
  } else if ([elementName isEqualToString:@"YEARTODATE"]) {
    OFX401kSummaryInfo* summary = currentObject;
    [self setCurrentObject:[objectStack lastObject]];
    if (currentObject != nil) {
      [currentObject setValue:summary forKey:@"yearToDateSummaryInfo"];
      [objectStack removeLastObject];
    }
  } else if ([elementName isEqualToString:@"INCEPTIONTODATE"]) {
    OFX401kSummaryInfo* summary = currentObject;
    [self setCurrentObject:[objectStack lastObject]];
    if (currentObject != nil) {
      [currentObject setValue:summary forKey:@"inceptionToDateSummaryInfo"];
      [objectStack removeLastObject];
    }
  } else if ([elementName isEqualToString:@"PERIODTODATE"]) {
    OFX401kSummaryInfo* summary = currentObject;
    [self setCurrentObject:[objectStack lastObject]];
    if (currentObject != nil) {
      [currentObject setValue:summary forKey:@"periodToDateSummaryInfo"];
      [objectStack removeLastObject];
    }
  } else if ([elementName isEqualToString:@"ACCTINFOTRNRS"]) {
    [document setAccountInfo:currentObject];
    currentObject = nil;
  } else if ([elementName isEqualToString:@"CONTRIBUTIONS"] || 
             [elementName isEqualToString:@"WITHDRAWALS"]   || 
             [elementName isEqualToString:@"EARNINGS"]      ||
             [elementName isEqualToString:@"INV401KBAL"]    || 
             [elementName isEqualToString:@"INV401K"]) {
    [self setParentElement:nil];
  } else if ([elementName isEqualToString:@"BANKACCTFROM"] ||
             [elementName isEqualToString:@"CCACCTFROM"] ||
             [elementName isEqualToString:@"INVACCTFROM"] || 
             [elementName isEqualToString:@"INVACCTINFO"] || 
             [elementName isEqualToString:@"BANKACCTINFO"] || 
             [elementName isEqualToString:@"CCACCTINFO"]) {
    if (([parentElement isEqualToString:@"INVACCTINFO"] &&
        [elementName isEqualToString:@"INVACCTFROM"]) || 
        ([parentElement isEqualToString:@"BANKACCTINFO"] &&
         [elementName isEqualToString:@"BANKACCTFROM"]) ||
        ([parentElement isEqualToString:@"CCACCTINFO"] &&
         [elementName isEqualToString:@"CCACCTFROM"])) {
      [self setParentElement:nil];
    } else {
      OFXAccount* account = currentObject;
      [self setCurrentObject:[objectStack lastObject]];
      if (currentObject != nil) {
        if ([messageSet isEqualToString:@"SIGNUPMSGSRSV1"]) {
          NSMutableArray* accountList = [currentObject valueForKey:@"accountList"];
          [accountList addObject:account];
        } else {
          [currentObject setValue:account forKey:@"account"];
        }
        [objectStack removeLastObject];
      }
    }
  } else if ([elementName isEqualToString:@"VESTINFO"]) { 
    OFX401kVestingInfo* vest = currentObject;
    [self setCurrentObject:[objectStack lastObject]];
    if (currentObject != nil) {
      [objectStack removeLastObject];
      [[currentObject valueForKey:@"vestingInfo"] addObject:vest];
    }
  } else if ([elementName isEqualToString:@"LOANINFO"]) {
    [self setParentElement:nil];
    OFX401kLoan* loan = currentObject;
    [self setCurrentObject:[objectStack lastObject]];
    if (currentObject != nil) {
      [objectStack removeLastObject];
      [[currentObject valueForKey:@"loanInfo"] addObject:loan];
    }    
  } else if ([elementName isEqualToString:@"BANKACCTTO"] || 
             [elementName isEqualToString:@"CCACCTTO"]) {
    OFXAccount* account = currentObject;
    [self setCurrentObject:[objectStack lastObject]];
    if (currentObject != nil) {
      [currentObject setValue:account forKey:@"transferToAccount"];
      [objectStack removeLastObject];
    }
  } else if ([elementName isEqualToString:@"PAYEEID"]) {
    OFXPayee* payee = currentObject;
    [self setCurrentObject:[objectStack lastObject]];
    [objectStack removeLastObject];
    [currentObject setValue:payee forKey:@"payee"];
  } else if ([elementName isEqualToString:@"PAYEE"]) { 
    OFXPayee* payee = currentObject;
    [self setCurrentObject:[objectStack lastObject]];
    [objectStack removeLastObject];
    [currentObject setValue:payee forKey:@"payee"];
    [self setParentElement:@""];
  } else if ([elementName isEqualToString:@"LEDGERBAL"] ||
             [elementName isEqualToString:@"AVAILBAL"]) {
    [self setParentElement:@""];
  } else if ([elementName isEqualToString:@"NAME"] && 
             [currentObject isKindOfClass:[OFXPayee class]] && 
             ![parentElement isEqualToString:@"PAYEE"]) { 
    OFXPayee* payee = currentObject;
    [self setCurrentObject:[objectStack lastObject]];
    [objectStack removeLastObject];
    [currentObject setValue:payee forKey:@"payee"];
  } else if ([elementName isEqualToString:@"IMAGEDATA"]) { 
    OFXImage* image = currentObject;
    [self setCurrentObject:[objectStack lastObject]];
    if (currentObject != nil) {
      [currentObject setValue:image forKey:@"image"];
      [objectStack removeLastObject];
    }
  } else if ([elementName isEqualToString:@"STMTTRN"]) {
    OFXTransaction* transaction = currentObject;
    if (inInvBankTran) {
      [transaction setValue:invBankTranFundingSubAccount 
                     forKey:@"fundingSubAccount"];
    }
    [self setCurrentObject:[objectStack lastObject]];
    if (currentObject != nil) {
      [[currentObject valueForKey:@"transactions"] addObject:transaction];
      [objectStack removeLastObject];
    }
  } else if ([elementName isEqualToString:@"BUYMF"] ||
             [elementName isEqualToString:@"BUYDEBT"] || 
             [elementName isEqualToString:@"BUYOPT"] || 
             [elementName isEqualToString:@"BUYOTHER"] || 
             [elementName isEqualToString:@"BUYSTOCK"] ||
             [elementName isEqualToString:@"CLOSUREOPT"] ||
             [elementName isEqualToString:@"INCOME"] || 
             [elementName isEqualToString:@"INVEXPENSE"] || 
             [elementName isEqualToString:@"JRNLFUND"] ||
             [elementName isEqualToString:@"JRNLSEC"] ||
             [elementName isEqualToString:@"MARGININTEREST"] || 
             [elementName isEqualToString:@"REINVEST"] ||
             [elementName isEqualToString:@"RETOFCAP"] ||
             [elementName isEqualToString:@"SELLDEBT"] ||
             [elementName isEqualToString:@"SELLMF"] ||
             [elementName isEqualToString:@"SELLOPT"] ||
             [elementName isEqualToString:@"SELLOTHER"] ||
             [elementName isEqualToString:@"SELLSTOCK"] ||
             [elementName isEqualToString:@"SPLIT"] ||
             [elementName isEqualToString:@"TRANSFER"]) {
    OFXTransaction* transaction = currentObject;
    [self setCurrentObject:[objectStack lastObject]];
    if (currentObject != nil) {
      [[currentObject valueForKey:@"transactions"] addObject:transaction];
      [objectStack removeLastObject];
    }
  } else if ([elementName isEqualToString:@"INVBANKTRAN"]) {
    [self setInInvBankTran:NO];
    [self setInvBankTranFundingSubAccount:nil];
  } else if ([elementName isEqualToString:@"POSMF"] ||
             [elementName isEqualToString:@"POSDEBT"] ||
             [elementName isEqualToString:@"POSMF"] ||
             [elementName isEqualToString:@"POSOPT"] ||
             [elementName isEqualToString:@"POSOTHER"] ||
             [elementName isEqualToString:@"POSSTOCK"]) {
    OFXInvestmentPosition* position = currentObject;
    [self setCurrentObject:[objectStack lastObject]];
    if (currentObject != nil) {
      [[currentObject valueForKey:@"positions"] addObject:position];
      [objectStack removeLastObject];
    }
  } 
  
  else if ([elementName isEqualToString:@"OOBUYDEBT"]   ||
             [elementName isEqualToString:@"OOBUYMF"]     ||
             [elementName isEqualToString:@"OOBUYOPT"]    ||
             [elementName isEqualToString:@"OOBUYOTHER"]  ||
             [elementName isEqualToString:@"OOBUYSTOCK"]  ||
             [elementName isEqualToString:@"OOSELLDEBT"]  ||
             [elementName isEqualToString:@"OOSELLMF"]    ||
             [elementName isEqualToString:@"OOSELLOPT"]   ||
             [elementName isEqualToString:@"OOSELLOTHER"] ||
             [elementName isEqualToString:@"OOSELLSTOCK"] ||
             [elementName isEqualToString:@"SWITCHMF"]) {
    OFXInvestmentOpenOrder* order = currentObject;
    [self setCurrentObject:[objectStack lastObject]];
    if (currentObject != nil) {
      [[currentObject valueForKey:@"openOrders"] addObject:order];
      [objectStack removeLastObject];
    }
  }
  
  else if ([elementName isEqualToString:@"CURRENCY"] ||
             [elementName isEqualToString:@"ORIGCURRENCY"]) {
    OFXCurrency* currency = currentObject;
    [self setCurrentObject:[objectStack lastObject]];
    if (currentObject != nil) {
      [currentObject setCurrency:currency];
      [objectStack removeLastObject];
    }
  } else if ([elementName isEqualToString:@"SECID"]) {
    if ([parentElement isEqualToString:@"SECINFO"]) {
      [self setParentElement:nil];
    } else {
      OFXSecurity* security = currentObject;
      [self setCurrentObject:[objectStack lastObject]];
      if (currentObject != nil) {
        [currentObject setValue:security forKey:@"security"];
        [objectStack removeLastObject];
      }
    }
  } else if ([elementName isEqualToString:@"STOCKINFO"] ||
             [elementName isEqualToString:@"MFINFO"] ||
             [elementName isEqualToString:@"OPTINFO"] ||
             [elementName isEqualToString:@"DEBTINFO"] ||
             [elementName isEqualToString:@"OTHERINFO"]) {
    NSString* securityId = [NSString stringWithFormat:@"%@@%@",
                            [currentObject valueForKey:@"uniqueId"],
                            [currentObject valueForKey:@"uniqueIdType"]];
    [securities setValue:currentObject forKey:securityId];
  } else if ([elementName isEqualToString:@"BAL"]) {
    [self setParentElement:nil];
    OFXBalance* balance = currentObject;
    [self setCurrentObject:[objectStack lastObject]];
    if (currentObject != nil) {
      NSMutableArray* balanceList = [currentObject valueForKey:@"otherBalances"];
      [balanceList addObject:balance];
      [objectStack removeLastObject];
    }
  } else if ([elementName isEqualToString:@"FI"]) {
    OFXFinancialInstitution* financialInstitution = currentObject;
    [self setCurrentObject:[objectStack lastObject]];
    if (currentObject != nil) {
      [objectStack removeLastObject];
      [currentObject setValue:financialInstitution 
                       forKey:@"financialInstitution"];
    }
  } else if ([elementName isEqualToString:@"PORTION"]) {
    OFXAssetClassPortion* portion = currentObject;
    [self setCurrentObject:[objectStack lastObject]];
    if (currentObject != nil) {
      [[currentObject valueForKey:@"assetClass"] addObject:portion];
      [objectStack removeLastObject];
    }
  } else if ([elementName isEqualToString:@"FIPORTION"]) {
    OFXAssetClassPortion* portion = currentObject;
    [self setCurrentObject:[objectStack lastObject]];
    if (currentObject != nil) {
      [[currentObject valueForKey:@"financialInstitutionAssetClass"] addObject:portion];
      [objectStack removeLastObject];
    }
  }
}

- (void)parser:(AQXMLParser *)parser 
foundAttributeDeclarationWithName:(NSString *)attributeName 
    forElement:(NSString *)elementName type:(NSString *)type 
  defaultValue:(NSString *)defaultValue
{
}

- (void)parser:(AQXMLParser *)parser foundCDATA:(NSData *)CDATABlock
{
}

- (void)parser:(AQXMLParser *)parser foundCharacters:(NSString *)string
{
  // normalize the string
  string = [string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
  NSString* stringTranslatedName = [stringTranslationTable valueForKey:currentElement];
  NSString* dateTranslatedName = [dateTranslationTable valueForKey:currentElement];
  NSString* numberTranslatedName = [numberTranslationTable valueForKey:currentElement];
  NSString* booleanTranslatedName = [booleanTranslationTable valueForKey:currentElement];
  NSString* urlTranslatedName = [urlTranslationTable valueForKey:currentElement];

  NSNumberFormatter* numberFormatter = [[NSNumberFormatter alloc] init];
  
  if (stringTranslatedName != nil) {
      [currentObject setValue:string forKey:stringTranslatedName];
  } else if (numberTranslatedName != nil &&
             !([parentElement isEqualToString:@"CONTRIBUTIONS"] || 
               [parentElement isEqualToString:@"WITHDRAWALS"] ||
               [parentElement isEqualToString:@"EARNINGS"])) {
    [currentObject setValue:[numberFormatter numberFromString:string]
                      forKey:numberTranslatedName];
  } else if (dateTranslatedName != nil) {
    [currentObject setValue:[self parseDateFromString:string] 
                     forKey:dateTranslatedName];
  } else if (booleanTranslatedName != nil) {
    if ([string isEqualToString:@"Y"]) {
      [currentObject setValue:[NSNumber numberWithBool:YES] forKey:booleanTranslatedName];
    } else {
      [currentObject setValue:[NSNumber numberWithBool:NO] forKey:booleanTranslatedName];
    }
  } else if (urlTranslatedName != nil) {
    NSURL* url = [NSURL URLWithString:string];
    [currentObject setValue:url forKey:urlTranslatedName];
  } else if ([currentElement isEqualToString:@"DTSERVER"]) {
    // signon response
    [document setServerDate:[self parseDateFromString:string]];
  } else if ([currentElement isEqualToString:@"LANGUAGE"]) {
    // signon response
    [document setLanguage:string];
  } else if ([currentElement isEqualToString:@"BALAMT"]) {
    if ([parentElement isEqualToString:@"LEDGERBAL"]) {
      [currentObject setValue:[numberFormatter numberFromString:string]
                       forKey:@"ledgerBalance"];
    } else if ([parentElement isEqualToString:@"AVAILBAL"]) {
      [currentObject setValue:[numberFormatter numberFromString:string]
                       forKey:@"availableBalance"];
    }
  } else if ([currentElement isEqualToString:@"SUBACCTFUND"]) {
    // if we are in an inv bank tran segment then we need to iterate over all
    // the previous bank transactions that do not have this value set and 
    // set it.  otherwise just set on current object.
    if (inInvBankTran) {
      // in well formed documents currentObject should be an investment 
      // statement.
      OFXInvestmentStatement* statement = currentObject;
      for (OFXTransaction* transaction in [statement transactions]) {
        if ([transaction isKindOfClass:[OFXInvestmentBankTransaction class]]) {
          if ([transaction valueForKey:@"fundingSubAccount"] == nil) {
            [transaction setValue:string forKey:@"fundingSubAccount"];
          }
        }
      }
      
      [self setInvBankTranFundingSubAccount:string];
    } else {
      [currentObject setValue:string forKey:@"fundingSubAccount"];
    }
  } else if ([currentElement isEqualToString:@"DTASOF"]) {
    if ([parentElement isEqualToString:@"LEDGERBAL"]) {
      [currentObject setValue:[self parseDateFromString:string]
                       forKey:@"ledgerBalanceValidAsOf"];
    } else if ([parentElement isEqualToString:@"AVAILBAL"]) {
      [currentObject setValue:[self parseDateFromString:string]
                       forKey:@"availableBalanceValidAsOf"];
    } else if ([parentElement isEqualToString:@"BAL"]) {
      [currentObject setValue:[self parseDateFromString:string]
                       forKey:@"validAsOf"];
    } else if ([parentElement isEqualToString:@"STMTRS"] ||
               [parentElement isEqualToString:@"CCSTMTRS"] ||
               [parentElement isEqualToString:@"INVSTMTRS"]) {
      [currentObject setValue:[self parseDateFromString:string]
                       forKey:@"statementAsOf"];
      [self setParentElement:nil];
    } else if ([parentElement isEqualToString:@"LOANINFO"]) {
      [currentObject setValue:[self parseDateFromString:string]
                       forKey:@"currentBalanceValidAsOf"];
    }
  } else if ([currentElement isEqualToString:@"FIASSETCLASS"]) {
    if ([parentElement isEqualToString:@"FIPORTION"]) {
      [currentObject setValue:string forKey:@"assetClass"];      
    } else {
      [currentObject setValue:string forKey:@"financialInstitutionAssetClass"];      
    }
    [self setParentElement:nil];
  } else if ([currentElement isEqualToString:@"PRETAX"]) {
    if ([parentElement isEqualToString:@"INV401KBAL"]) {
      [currentObject setValue:[numberFormatter numberFromString:string]
                       forKey:@"preTaxSecurityValue"];
    } else {
      NSString* keyName = [NSString stringWithFormat:@"preTax%c%@", 
                           [parentElement characterAtIndex:0],
                           [[parentElement lowercaseString] substringWithRange:NSMakeRange(1, [parentElement length]-1)]];
      [currentObject setValue:[numberFormatter numberFromString:string]
                       forKey:keyName];
    }
  } else if ([currentElement isEqualToString:@"AFTERTAX"]) {
    if ([parentElement isEqualToString:@"INV401KBAL"]) {
      [currentObject setValue:[numberFormatter numberFromString:string]
                       forKey:@"afterTaxSecurityValue"];
    } else {
      NSString* keyName = [NSString stringWithFormat:@"afterTax%c%@", 
                           [parentElement characterAtIndex:0],
                           [[parentElement lowercaseString] substringWithRange:NSMakeRange(1, [parentElement length]-1)]];
      [currentObject setValue:[numberFormatter numberFromString:string]
                       forKey:keyName];    
    }
  } else if ([currentElement isEqualToString:@"MATCH"]) {
    if ([parentElement isEqualToString:@"INV401KBAL"]) {
      [currentObject setValue:[numberFormatter numberFromString:string]
                       forKey:@"valueOfSecuritiesPurchasedWithMatch"];
    } else {
      NSString* keyName = [NSString stringWithFormat:@"matching%c%@", 
                           [parentElement characterAtIndex:0],
                           [[parentElement lowercaseString] substringWithRange:NSMakeRange(1, [parentElement length]-1)]];
      [currentObject setValue:[numberFormatter numberFromString:string]
                       forKey:keyName];    }
  } else if ([currentElement isEqualToString:@"PROFITSHARING"]) {
    if ([parentElement isEqualToString:@"INV401KBAL"]) {
      [currentObject setValue:[numberFormatter numberFromString:string]
                       forKey:@"valueOfSecuritiesPurchasedWithProfitSharing"];
    } else {
      NSString* keyName = [NSString stringWithFormat:@"profitSharing%c%@", 
                           [parentElement characterAtIndex:0],
                           [[parentElement lowercaseString] substringWithRange:NSMakeRange(1, [parentElement length]-1)]];
      [currentObject setValue:[numberFormatter numberFromString:string]
                       forKey:keyName];    }
  } else if ([currentElement isEqualToString:@"ROLLOVER"]) {
    if ([parentElement isEqualToString:@"INV401KBAL"]) {
      [currentObject setValue:[numberFormatter numberFromString:string]
                       forKey:@"valueOfRollOverSecurities"];
    } else {
      NSString* keyName = [NSString stringWithFormat:@"rollOver%c%@", 
                           [parentElement characterAtIndex:0],
                           [[parentElement lowercaseString] substringWithRange:NSMakeRange(1, [parentElement length]-1)]];
      [currentObject setValue:[numberFormatter numberFromString:string]
                       forKey:keyName];    }
  } else if ([currentElement isEqualToString:@"OTHERVEST"]) {
    if ([parentElement isEqualToString:@"INV401KBAL"]) {
      [currentObject setValue:[numberFormatter numberFromString:string]
                       forKey:@"valueOfSecuritiesPurchasedWithOtherVestingFunds"];
    } else {
      NSString* keyName = [NSString stringWithFormat:@"otherVesting%c%@", 
                           [parentElement characterAtIndex:0],
                           [[parentElement lowercaseString] substringWithRange:NSMakeRange(1, [parentElement length]-1)]];
      [currentObject setValue:[numberFormatter numberFromString:string]
                       forKey:keyName];    }
  } else if ([currentElement isEqualToString:@"OTHERNONVEST"]) {
    if ([parentElement isEqualToString:@"INV401KBAL"]) {
      [currentObject setValue:[numberFormatter numberFromString:string]
                       forKey:@"valueOfSecuritiesPurchasedWithOtherNonVestingFunds"];
    } else {
      NSString* keyName = [NSString stringWithFormat:@"otherNonVesting%c%@", 
                           [parentElement characterAtIndex:0],
                           [[parentElement lowercaseString] substringWithRange:NSMakeRange(1, [parentElement length]-1)]];
      [currentObject setValue:[numberFormatter numberFromString:string]
                       forKey:keyName];
    }
  } else if ([currentElement isEqualToString:@"TOTAL"]) {
      NSString* keyName = [NSString stringWithFormat:@"total%c%@", 
                           [parentElement characterAtIndex:0],
                           [[parentElement lowercaseString] substringWithRange:NSMakeRange(1, [parentElement length]-1)]];
      [currentObject setValue:[numberFormatter numberFromString:string]
                       forKey:keyName];
  }
}

- (void)parser:(AQXMLParser *)parser
parseErrorOccurred:(NSError *)parseError
{
  // LibXML2 Error 1549 is just a warning about resolving external entities.
  // Ignore it.
  if ([parseError code] != 1549)
  {
    NSLog(@"Parse error occurred, %@", [parseError localizedDescription]);
  }
}

@end
